## 前言
上篇和大家介紹Filter
去是如何取得且我們可以透過IOC容器註冊IFilterProvider
來擴充取得Filter
注入點.
在ASP.NET MVC的Filter
,在執行目標前後彈性擴充額外操作(繼承ActionFilter
並掛Attribute
),這是一種典型的AOP
設計模式
本篇會和大家繼續分享InvokeAction
後續動作.
為什麼我們在Action
方法和Controller
類別放置一個繼承(AuthorizationFilter、ActionFilter、ResultFilter,ExceptionFilter
)標籤(Attribute
)對應介面(IAuthorizationFilter、IActionFilter、IResultFilter,IExceptionFilter
),程式幫我們自動載入MVC生命週期中並執行?
我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
AOP 是 OOP(物件導向)一個變化程式撰寫思想。(非取代OOP而是擴充)
導入AOP幫助:
可幫我們分離核心邏輯跟非核心邏輯代碼,很好降低模組間耦合性,已便日後擴充。
非核心邏輯代碼像:(日誌記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來)
原本寫法把寫日誌相關程式寫入,業務邏輯方法中。導致此方法非單一職則。我們可以把程式重構改寫成(右圖),將寫日誌方法抽離出來更有效達成模組化。
AOP
是擴充Proxy Pattern
(代理模式)概念,為每個方法提供一個代理人,可為執行前或執行後提供擴展機制,並由代理類別來呼叫真正呼叫使用方法.
如果想要更多了解代理模式可以參考我之前寫的ProxyPattern代理模式(二)
在Asp.net MVC有五個過濾器實現AOP
架構
下面順序案照執行呼叫執行順序來介紹
IAuthenticationFilter
:最一開始執行驗證使用過濾器,這個介面有一個void OnAuthentication(AuthenticationContext filterContext)
方法.如果驗證失敗可以對於filterContext.Result
設值來結束這次請求.IAuthorizationFilter
:執行過程和IAuthenticationFilter
過濾器基本上一樣IActionFilter
:提供方法執行前,後的動作.IResultFilter
:提供方法執行結果前,後的動作.IExceptionFilter
:在執行此方法有錯誤時觸發的過濾器.MVC上面幾個過濾器,讓開發者可以很有彈性擴充自己的系統且不用動到核心原始碼.很好達到開放封閉原則
AuthorizationFilter
在ActionInvoker
執行前第一項工作,因為後續工作(參數模型綁定,參數模型驗證,呼叫方法)只有在驗證成功的基礎上才會有意義。
一開始呼叫InvokeAuthenticationFilters
方法來取得AuthenticationContext
物件,在判斷authenticationContext.Result
是否有給值.如果有當作驗證失敗不用在執行後面流程.
try
{
AuthenticationContext authenticationContext = InvokeAuthenticationFilters(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor);
if (authenticationContext.Result != null)
{
AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
authenticationContext.Result);
InvokeActionResult(controllerContext, challengeContext.Result ?? authenticationContext.Result);
}
else
{
//.....
}
}
protected virtual AuthenticationContext InvokeAuthenticationFilters(
ControllerContext controllerContext,
IList<IAuthenticationFilter> filters,
ActionDescriptor actionDescriptor)
{
//....
AuthenticationContext context = new AuthenticationContext(controllerContext, actionDescriptor,
originalPrincipal);
foreach (IAuthenticationFilter filter in filters)
{
filter.OnAuthentication(context);
// short-circuit evaluation when an error occurs
if (context.Result != null)
{
break;
}
}
IPrincipal newPrincipal = context.Principal;
if (newPrincipal != originalPrincipal)
{
Contract.Assert(context.HttpContext != null);
context.HttpContext.User = newPrincipal;
Thread.CurrentPrincipal = newPrincipal;
}
return context;
}
AuthenticationContext
中重要的一個屬性是
public ActionResult Result { get; set; }
只要這個物件不為null
就會直接返回此次請求.在方法中我封裝一個AuthenticationContext
物件,把它當作參數傳入IAuthenticationFilter.OnAuthentication
方法中(這就是我們在繼承AuthenticationFilter
使用AuthenticationContext
物件)
值得一提程式會判斷context.Result
是否為null
來當作迴圈中斷點.
if (context.Result != null)
{
break;
}
這個邏輯是我們對於Authentication
驗證失敗後想要直接返回請求可以透過把context.Result
給一個值(ActionResult
物件),外面會照authenticationContext.Result
是否為null
為依據判斷是否繼續執行後面動作.
下一個步驟是檢驗IAuthorizationFilter
過濾器,執行過程和IAuthenticationFilter
過濾器基本上一樣
依照物件內Result
屬性是否為null
來當作後續執行依據.
AuthorizationContext authorizationContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
if (authorizationContext.Result != null)
{
AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
authorizationContext.Result);
InvokeActionResult(controllerContext, challengeContext.Result ?? authorizationContext.Result);
}
public interface IAuthorizationFilter
{
void OnAuthorization(AuthorizationContext filterContext);
}
public class AuthorizationContext : ControllerContext
{
//.....
public virtual ActionDescriptor ActionDescriptor { get; set; }
public ActionResult Result { get; set; }
}
既然IAuthenticationFilter
和IAuthorizationFilter
過濾器驗證東西都很類似為什麼要分成兩個呢?
仔細比較會發現IAuthenticationFilter
多了(設置Principal
),檢驗方式。
ActionDescriptor
(使用ReflectedActionDescriptor
)這個物件存放目前執行Action
相關的資訊(裡面有一個Execute
抽象方法,靠他來做Action
呼叫使用)
protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
{
actionResult.ExecuteResult(controllerContext);
}
如果判斷權限錯誤或Filter
需提前返回Result
就會執行InvokeActionResult
方法,來執行返回工作.
有在寫Asp.net MVC的人一定對於下面這個介面不陌生,這個過濾器在InvokeActionMethodFilter
使用時被呼叫.
ActionExecutingContext
也有一個Result
物件用此判斷是否有執行後續請求.一般也是NULL
ActionExecutingContext
這個物件比其他過濾器參數多了一個重要的成員IDictionary<string, object> parameters
,有這個成員我們可以針對呼叫Action
參數處理.
public interface IActionFilter
{
void OnActionExecuting(ActionExecutingContext filterContext);
void OnActionExecuted(ActionExecutedContext filterContext);
}
internal static ActionExecutedContext InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func<ActionExecutedContext> continuation)
{
//執行Action 過濾器
filter.OnActionExecuting(preContext);
//如果有Result 直接返回
if (preContext.Result != null)
{
return new ActionExecutedContext(preContext, preContext.ActionDescriptor, true /* canceled */, null /* exception */)
{
Result = preContext.Result
};
}
bool wasError = false;
ActionExecutedContext postContext = null;
try
{
postContext = continuation();
}
catch (ThreadAbortException)
{
postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, null /* exception */);
//執行Action後 過濾器
filter.OnActionExecuted(postContext);
throw;
}
catch (Exception ex)
{
wasError = true;
postContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false /* canceled */, ex);
filter.OnActionExecuted(postContext);
if (!postContext.ExceptionHandled)
{
throw;
}
}
if (!wasError)
{
filter.OnActionExecuted(postContext);
}
return postContext;
}
其中有一段continuation
這個委派是InvokeActionMethod
這個方法,這個方法取得使用Action
方法.
protected virtual ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
{
object returnValue = actionDescriptor.Execute(controllerContext, parameters);
ActionResult result = CreateActionResult(controllerContext, actionDescriptor, returnValue);
return result;
}
try
{
postContext = continuation();
}
ActionExecutedContext
物件中的Result
屬性就是執行Action
方法後的結果
呼叫InvokeActionResult
過濾器藉由InvokeActionResultFilterRecursive
方法
這個方法使用遞迴方式看之前的使用for loop
執行過濾器方式有所不同,幸好在原始碼有註解.
主要是因為下面原因
OnResultExecuting
事件必須按正向順序觸,發然後必須觸發InvokeActionResult
(執行Action
動作方法),OnResultExecuted
事件必須以相反的順序觸發
private ResultExecutedContext InvokeActionResultFilterRecursive(IList<IResultFilter> filters, int filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult)
{
if (filterIndex > filters.Count - 1)
{
InvokeActionResult(controllerContext, actionResult);
return new ResultExecutedContext(controllerContext, actionResult, canceled: false, exception: null);
}
IResultFilter filter = filters[filterIndex];
filter.OnResultExecuting(preContext);
if (preContext.Cancel)
{
return new ResultExecutedContext(preContext, preContext.Result, canceled: true, exception: null);
}
bool wasError = false;
ResultExecutedContext postContext = null;
try
{
int nextFilterIndex = filterIndex + 1;
postContext = InvokeActionResultFilterRecursive(filters, nextFilterIndex, preContext, controllerContext, actionResult);
}
catch (ThreadAbortException)
{
postContext = new ResultExecutedContext(preContext, preContext.Result, canceled: false, exception: null);
filter.OnResultExecuted(postContext);
throw;
}
catch (Exception ex)
{
wasError = true;
postContext = new ResultExecutedContext(preContext, preContext.Result, canceled: false, exception: ex);
filter.OnResultExecuted(postContext);
if (!postContext.ExceptionHandled)
{
throw;
}
}
if (!wasError)
{
filter.OnResultExecuted(postContext);
}
return postContext;
}
在OnResultExecuting
方法的ResultExecutingContext
可以藉由Canceled
這個屬性來最後控制是否要執行Action
方法,如果不要將這個值設定為false
.
public virtual bool Canceled { get; set; }
最後介紹錯誤時呼叫的過濾器IExceptionFilter
可以看到在執行方法的最前面使用了一個try....catch
而最後catch
程式碼如下.
在這個方法中有一個重要的屬性是bool ExceptionHandled
,如果在錯誤時設定為true
她就會執行Result
的結果(因為最後呼叫了InvokeActionResult
方法.
//....
catch (Exception ex)
{
// 錯誤處理過濾器
ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
//如果需要自己處理錯誤 exceptionContext.ExceptionHandled 設為true
if (!exceptionContext.ExceptionHandled)
{
throw;
}
InvokeActionResult(controllerContext, exceptionContext.Result);
}
protected virtual ExceptionContext InvokeExceptionFilters(ControllerContext controllerContext, IList<IExceptionFilter> filters, Exception exception)
{
ExceptionContext context = new ExceptionContext(controllerContext, exception);
foreach (IExceptionFilter filter in filters.Reverse())
{
filter.OnException(context);
}
return context;
}
過濾器這部分原始碼很值得大家探討,因為在主流IOC
容器框架有支援AOP
概念.
AOP
有很大優點是可做到設計五大原則的其中兩項
使程式碼耦合性變低
執行Action
方法前,如何取得權限過濾器並呼叫檢驗,另外在呼叫方法前可以看到會把用到的資訊封裝到一個Context
物件中.
IAuthenticationFilter
和IAuthorizationFilter
基本上都是權限驗證的過濾器
但有先後順序,這點需注意!! 先執行
IAuthenticationFilter
後IAuthorizationFilter
看了MVC過濾器原始碼後有感而法,石頭就基於RealProxy這個類別做了一個AOP開源框架AwesomeProxy.Net.
下篇會繼續介紹Action
參數如何建立,遇到複雜Model
MVC是怎麼處理